/*
	Module:			FBPA_Parallel_main.cpp
	Author:			Brooke Thrower and Jeramie Hallyburton
	Date:			Nov. 21, 2011
	Description:	read 2 files that contain coordinates of residences (residence.dat) and foodbanks (foodbanks.dat)
					in Toronto.  Determine the shortest straight line distance between the residence and the a foodbank and
					put them into categories based on distances then report to user.  This application can work with 1 to many processes
*/
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <string>
#include <mpi.h>
#include <cmath>

using namespace std;

//# of bytes per record
const int RESIDENCE_BYTES = 24; 

char* residence_fname = "residences.dat";
char* foodbank_fname = "foodbanks.dat";

// Define a structure for storing x,y cooridnates
typedef struct
{
	double x, y;
} Coord_t;

//structure used for passing information
struct Range
{
	int zeroToOne, oneToTwo, twoToFive, greaterThanFive, recordsProcessed;
	double perZeroToOne, perOneToTwo, perTwoToFive, perGreaterThanFive;
	Range() : zeroToOne( 0 ), oneToTwo( 0 ), twoToFive( 0 ), greaterThanFive( 0 ), recordsProcessed( 0 ), perZeroToOne( 0 ), perOneToTwo( 0 ), perTwoToFive( 0 ), perGreaterThanFive( 0 )  { }
};


double totalTime;
int nAddresses = 0;
Range ranges;
MPI_Datatype Range_Type;

vector<Coord_t> vFoodBanks;

/*
 * Name: fileSize()
 * Purpose: determine the size of a file
 * Input: char* filePath - the path to the file being processed
 * Output: streampos - the size of the file
 */
streampos fileSize( const char* filePath ){
	//determine the size of the file
    streampos fsize = 0;
    ifstream file( filePath, ios::binary );

    fsize = file.tellg();
    file.seekg( 0, ios::end );
    fsize = file.tellg() - fsize;
    file.close();

    return fsize;
}

/*
 * Name: processResidences()
 * Purpose: process each set of residence coordinates and determine the shortest distance to a foodbank.  Store the information
			and report to the master.  The master process will then diplay a report of the findings.
 * Input: None
 * Output: None
 */
void processResidences( int rank, int numProcs ) {
	try {
		// Open the input file
		ifstream in( residence_fname );
		//get the size of the residence file
		streampos filesize = fileSize( residence_fname );

		//number of records to process
		int nresidences = (int)filesize / RESIDENCE_BYTES;
		//set starting position
		int curpos = rank;

		while( curpos <  nresidences ) {
			
			in.seekg( curpos * RESIDENCE_BYTES ); 
			nAddresses++;
			double x, y;
			in >> x >> y;


			//temp ranges
			Range temp;
			temp.zeroToOne = 0;

			//find distance to nearest foodbank
			for( int i = 0; i < vFoodBanks.size(); ++i ) {
				
				double distance = std::sqrt(  ( ( x - vFoodBanks[i].x ) * ( x - vFoodBanks[i].x ) ) +
										( ( y - vFoodBanks[i].y ) * ( y - vFoodBanks[i].y ) ) );


				
				//convert distance from metres to km
				distance /= 1000;

				if( distance <= 1.0 ) {
					temp.zeroToOne++;
					//can't get closer than this so exit
					break;
				} else if( distance > 1.0 && distance <= 2.0 ) {
					temp.oneToTwo++;
				} else if( distance > 2.0 && distance <= 5.0 ) {
					temp.twoToFive++;
				} else {
					temp.greaterThanFive++;
				}		
			}

			//commit closest foodbank to master table
			if( temp.zeroToOne > 0 )
				ranges.zeroToOne++;
			else if( temp.oneToTwo > 0 )
				ranges.oneToTwo++;
			else if( temp.twoToFive > 0 )
				ranges.twoToFive++;
			else
				ranges.greaterThanFive++;

			ranges.recordsProcessed++;

			//increment position
			curpos += numProcs;
		}

		//determine percentages and add to ranges
		ranges.perZeroToOne = (static_cast<double>( ranges.zeroToOne ) / nAddresses) * 100;
		ranges.perOneToTwo = (static_cast<double>( ranges.oneToTwo )/ nAddresses) * 100;
		ranges.perTwoToFive = (static_cast<double>( ranges.twoToFive ) / nAddresses) * 100;
		ranges.perGreaterThanFive = (static_cast<double>( ranges.greaterThanFive ) / nAddresses) * 100;

		Range* recvRanges;
		recvRanges = ( Range* ) malloc( numProcs * sizeof( Range ) );
		
		//if its the master "gather" information and display
		if (rank == 0) {
			//determine the total time to process
			totalTime = MPI_Wtime() - totalTime;
			//get the information for all processes
			MPI_Gather( &ranges, 1, Range_Type, recvRanges, 1, Range_Type, 0, MPI_COMM_WORLD);

			int zeroToOneTotal = 0, oneToTwoTotal = 0, twoToFiveTotal = 0, greaterThanFiveTotal = 0, recordsProcessedTotal = 0;
			double perZeroToOneTotal, perOneToTwoTotal, perTwoToFiveTotal, perGreaterThanFiveTotal;

			//print out report
			cout << "Proximity of Residential Addresses to Foodbanks in Toronto" << endl;
			cout << "----------------------------------------------------------" << endl << endl;

			cout << setw(27) << left << "Number of processes:" << numProcs << endl;
			cout << setw(27) << left << "Elapsed Time in Seconds:" << totalTime << endl << endl;
			//display information for each process
			for( int i = 0; i < numProcs; ++i ) {

				cout << "Process #" << i + 1 << " Results for " << recvRanges[i].recordsProcessed << " Addresses..." << endl << endl;

				cout << setw(20) << left << "Nearest Foodbank (km)" << setw(20) << right << "# of Addresses" << setw(20) << "% of Addresses" << endl;
				cout << setw(20) << left << "---------------------" << setw(20) << right << "--------------" << setw(20) << "--------------" << endl;
				cout << setw(20) << left << " 0 - 1" << setw(20) << right << recvRanges[i].zeroToOne << setw(20) <<  recvRanges[i].perZeroToOne << endl;
				cout << setw(20) << left << " 1 - 2" << setw(20) << right << recvRanges[i].oneToTwo << setw(20) << recvRanges[i].perOneToTwo << endl;
				cout << setw(20) << left << " 2 - 5" << setw(20) << right << recvRanges[i].twoToFive << setw(20) << recvRanges[i].perTwoToFive << endl;
				cout << setw(20) << left << " > 5" << setw(20) << right << recvRanges[i].greaterThanFive << setw(20) << recvRanges[i].perGreaterThanFive << endl << endl;

				zeroToOneTotal += recvRanges[i].zeroToOne;
				oneToTwoTotal += recvRanges[i].oneToTwo;
				twoToFiveTotal += recvRanges[i].twoToFive;
				greaterThanFiveTotal += recvRanges[i].greaterThanFive;
				recordsProcessedTotal += recvRanges[i].recordsProcessed;
			}
			//get the total percentages
			perZeroToOneTotal = (static_cast<double>( zeroToOneTotal ) / recordsProcessedTotal) * 100;
			perOneToTwoTotal = (static_cast<double>( oneToTwoTotal )/ recordsProcessedTotal) * 100;
			perTwoToFiveTotal = (static_cast<double>( twoToFiveTotal ) / recordsProcessedTotal) * 100;
			perGreaterThanFiveTotal = (static_cast<double>( greaterThanFiveTotal ) / recordsProcessedTotal) * 100;
			//display the combined results
			cout << "Aggregate Results for all " << recordsProcessedTotal << " Addresses..." << endl << endl;

			cout << setw(20) << left << "Nearest Foodbank (km)" << setw(20) << right << "# of Addresses" << setw(20) << "% of Addresses" << endl;
			cout << setw(20) << left << "---------------------" << setw(20) << right << "--------------" << setw(20) << "--------------" << endl;
			cout << setw(20) << left << " 0 - 1" << setw(20) << right << zeroToOneTotal << setw(20) <<  perZeroToOneTotal << endl;
			cout << setw(20) << left << " 1 - 2" << setw(20) << right << oneToTwoTotal << setw(20) << perOneToTwoTotal << endl;
			cout << setw(20) << left << " 2 - 5" << setw(20) << right << twoToFiveTotal << setw(20) << perTwoToFiveTotal << endl;
			cout << setw(20) << left << " > 5" << setw(20) << right << greaterThanFiveTotal << setw(20) << perGreaterThanFiveTotal << endl << endl;
		} else { //all other processes send information to the master
			MPI_Gather( &ranges, 1, Range_Type, recvRanges, 1, Range_Type, 0, MPI_COMM_WORLD);
		}
	} catch(std::exception ex) {
		std::cerr << ex.what() << std::endl;
	}
}

int main( int argc, char* argv[] ) {
	
	if( MPI_Init( &argc, &argv ) == MPI_SUCCESS ) {
		// Get # of procs, rank
		int numProcs, rank;
		MPI_Comm_size( MPI_COMM_WORLD, &numProcs );
		MPI_Comm_rank( MPI_COMM_WORLD, &rank );

		// Prepare input arguments
		int blocklens[] = {5, 4};
		MPI_Aint indices[2];
		indices[0] = 0;
		MPI_Type_extent(MPI_DOUBLE, &indices[1]);
		indices[1] *= 3;
		MPI_Datatype oldtypes[] = { MPI_INT, MPI_DOUBLE};

		// Construct the datatype
		MPI_Type_struct(2, blocklens, indices, oldtypes, &Range_Type);
		MPI_Type_commit(&Range_Type);

		if (rank == 0 )
		{
			totalTime = MPI_Wtime();
		}
		//read in food bank data		
		ifstream in( foodbank_fname );

		if( !in ) { 
			cout << "Could not open file: " << foodbank_fname << endl;
			return 1;
		} 
		//read in coordinates
		while( !in.eof() ) {
			Coord_t bank;
			in >> bank.x >> bank.y;
			vFoodBanks.push_back( bank );
		}
		
		//process residence data
		processResidences( rank, numProcs );
		//cleanup
		MPI_Type_free(&Range_Type);
		MPI_Finalize();
	}

	return 0;
}